/*
 * Hyuga: A Density-Grid Stream Clustering Platform.
 * Copyright (C) 2014 PUC-Rio/Laboratory for Advanced Collaboration
 *
 * Hyuga is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Hyuga is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Hyuga.  If not, see <http://www.gnu.org/licenses/>.
 */
package br.pucrio.inf.lac.konoha.hyuga.others.csv;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import br.pucrio.inf.lac.konoha.hyuga.core.Output;
import br.pucrio.inf.lac.konoha.hyuga.core.output.CSVEntry;
import br.pucrio.inf.lac.konoha.hyuga.core.output.OutputType;

public class CSVRandIndex {
    File dg2cepFile;
    File dbscanFile;
    PrintWriter randIndexFW;
    PrintWriter discoverFW;
    PrintWriter disperseFW;
    
    Output outSingleton;
    
    private Map<Long, List<CSVEntry>> dbscanSecBySecEntries;
        
    public CSVRandIndex(String dg2cep, String dbscan, String output) {
        dg2cepFile = new File(dg2cep);
        dbscanFile = new File(dbscan);
        outSingleton = Output.getInstance();
        
        if (!dg2cepFile.exists()) {
            System.out.println("File " + dg2cepFile.getName() + " does not exists.");
            System.exit(-1);
        }
        
        if (!dbscanFile.exists()) {
            System.out.println("File " + dbscanFile.getName() + " does not exists.");
            System.exit(-1);
        }

        try {
            File randIndexFile = new File(output + "randIndex.csv");
            randIndexFile.createNewFile();
            randIndexFW = new PrintWriter(new BufferedWriter(new FileWriter(randIndexFile)));
        
            File discoverFile = new File(output + "discover.csv");
            discoverFile.createNewFile();
            discoverFW = new PrintWriter(new BufferedWriter(new FileWriter(discoverFile)));
            
            File disperseFile = new File(output + "disperse.csv");
            disperseFile.createNewFile();
            disperseFW = new PrintWriter(new BufferedWriter(new FileWriter(disperseFile)));
        } catch (IOException ex) {
            System.out.println("Could not create output files");
            System.exit(-1);
        }
    }
    
    public CSVEntry getDG2CEPEntry(String csvLine) {
        // FirstDivision [0] timestamp,type,[cell],size
        // FirstDivision [1] cellcontent
        String[] firstDivision = csvLine.split("\"");
        
        String[] entrydata = firstDivision[0].split(",");
        long timestamp = Long.parseLong(entrydata[0]);
        OutputType type = OutputType.valueOf(entrydata[1]);
        int size = 0;
        if (entrydata.length == 4) size = Integer.parseInt(entrydata[3]);
        if (entrydata.length == 3) size = Integer.parseInt(entrydata[2]);

        Set<String> contentSet = new HashSet<String>();
        for (String node : firstDivision[1].split(",")) {
            contentSet.add(node);
        }
                
        return new CSVEntry(timestamp,type,size,csvLine,contentSet);
    }
    
    public CSVEntry getDBSCANEntry(String csvLine) {
        // FirstDivision [0] timestamp,snapshotsize,totaltime,size
        // FirstDivision [1] cellcontent
        String[] firstDivision = csvLine.split("\"");
        
        String[] entrydata = firstDivision[0].split(",");
        long timestamp = Long.parseLong(entrydata[0]);
        OutputType type = OutputType.DBSCAN;
        int size = Integer.parseInt(entrydata[3]);

        Set<String> contentSet = new HashSet<String>();
        for (String node : firstDivision[1].split(",")) {
            contentSet.add(node);
        }
                
        return new CSVEntry(timestamp,type,size,csvLine,contentSet);
    }

    private void calculateAllRandIndex() throws IOException {
        BufferedReader dg2cepBuffer = new BufferedReader(new FileReader(dg2cepFile));
        BufferedReader dbscanBuffer = new BufferedReader(new FileReader(dbscanFile));
        
        dbscanSecBySecEntries = dbscanSecBySecParser(dbscanBuffer);
        dbscanBuffer.close();
        long dbscanMinimumTimestamp = dbscanSecBySecEntries.keySet().iterator().next();
        
        String dg2cepCSVLine = dg2cepBuffer.readLine(); // header
        System.out.println("dg2cep,dbscan,randindex");
        
        while((dg2cepCSVLine = dg2cepBuffer.readLine()) != null) {
            CSVEntry dg2cepEntry = getDG2CEPEntry(dg2cepCSVLine);
            long entryTimestamp = dg2cepEntry.getTimestamp();
            
            if (entryTimestamp < dbscanMinimumTimestamp) {
                continue;
            } else if (dg2cepEntry.getType() == OutputType.DISPERSE) {
                measureDisperseEntry(dg2cepEntry);
            } else if (dg2cepEntry.getType() == OutputType.ADD) {
                measureAddEntry(dg2cepEntry);
            } else {
                long padded = entryTimestamp / 1000;
                long before = padded * 1000;
                long after  = (padded + 1) * 1000;
                
                List<CSVEntry> beforeList = dbscanSecBySecEntries.get(before);
                List<CSVEntry> afterList  = dbscanSecBySecEntries.get(after);
                
                CSVEntry dbscanBeforeEntry = getHigherRandIndex(dg2cepEntry, beforeList);
                CSVEntry dbscanAfterEntry  = getHigherRandIndex(dg2cepEntry, afterList);
                double beforeRandIndex = 0;
                double afterRandIndex  = 0;
                
                if (dbscanBeforeEntry != null) 
                    beforeRandIndex = randIndex(dg2cepEntry, dbscanBeforeEntry);
                
                if (dbscanAfterEntry != null)
                    afterRandIndex = randIndex(dg2cepEntry, dbscanAfterEntry);
                
                if (beforeRandIndex == 0) {
                    System.out.println("uai");
                    getHigherRandIndex(dg2cepEntry, beforeList);
                    beforeRandIndex = randIndex(dg2cepEntry, dbscanBeforeEntry);
                    
                } else if (afterRandIndex == 0) {
                    System.out.println("tenso");
                    afterRandIndex = randIndex(dg2cepEntry, dbscanAfterEntry);
                }
                try {
                if (beforeRandIndex > afterRandIndex)
                    System.out.println(dg2cepEntry.getSize() + "," + dbscanBeforeEntry.getSize() + "," + beforeRandIndex);
                else
                    System.out.println(dg2cepEntry.getSize() + "," + dbscanAfterEntry.getSize() + "," + afterRandIndex);
                } catch (Exception ex) {
//                    ex.printStackTrace();
                }
            }
        }
        randIndexFW.flush();
        discoverFW.flush();
        disperseFW.flush();
        randIndexFW.close();
        discoverFW.close();
        disperseFW.close();
    }
    
    private void measureAddEntry(CSVEntry dg2cepEntry) {
        long padded = dg2cepEntry.getTimestamp() / 1000;
        long before = (padded) * 1000;
        
        CSVEntry dbscanBeforeEntry = null;
        do {
            dbscanBeforeEntry = getHigherRandIndex(dg2cepEntry, dbscanSecBySecEntries.get(before));
            before -= 1000;
        } while (randIndex(dg2cepEntry, dbscanBeforeEntry) > 0.6);
        
        int diff = (int) (dg2cepEntry.getTimestamp() - dbscanBeforeEntry.getTimestamp());
        int ins = diff / 1000;
        discoverFW.println(dg2cepEntry.getSize() + "," + diff);
        System.out.println(dg2cepEntry.getSize() + "," + diff);
    }

    private void measureDisperseEntry(CSVEntry dg2cepEntry) {
        long padded = dg2cepEntry.getTimestamp() / 1000;
        long before = padded * 1000;
        
        CSVEntry dbscanBeforeEntry = null;
        do {
            dbscanBeforeEntry = getHigherRandIndex(dg2cepEntry, dbscanSecBySecEntries.get(before));
            before -= 1000;
        } while (randIndex(dg2cepEntry, dbscanBeforeEntry) < 0.1  && before > 0);
        
        if (dbscanBeforeEntry != null) {
            int diff = (int) (dg2cepEntry.getTimestamp() - dbscanBeforeEntry.getTimestamp());
            disperseFW.println(dg2cepEntry.getSize() + "," + diff);
            System.out.println(dg2cepEntry.getSize() + "," + diff);
        }
    }
    
    private Map<Long,List<CSVEntry>> dbscanSecBySecParser(BufferedReader dbscanBuffer) {
        Map<Long,List<CSVEntry>> dbscanSecBySecEntries = new TreeMap<Long, List<CSVEntry>>();
        String dbscanCSVLine = "";
        
        try {
            while((dbscanCSVLine = dbscanBuffer.readLine()) != null) {
                CSVEntry dbscanEntry = getDBSCANEntry(dbscanCSVLine);
                long entryTimestamp = dbscanEntry.getTimestamp();

                if (!dbscanSecBySecEntries.containsKey(entryTimestamp)) {
                    dbscanSecBySecEntries.put(entryTimestamp, new ArrayList<CSVEntry>());
                }
                
                List<CSVEntry> clusterListInThisSecond = dbscanSecBySecEntries.get(entryTimestamp);
                clusterListInThisSecond.add(dbscanEntry);
                dbscanSecBySecEntries.put(entryTimestamp, clusterListInThisSecond);
            }
        } catch (IOException e) {
            System.out.println("Exception at reading dbscan output file");
        }
        
        return dbscanSecBySecEntries;
    }
    
    private CSVEntry getHigherRandIndex(CSVEntry dg2cepEntry, List<CSVEntry> dbscanClustersInThisSecond) {
        CSVEntry dbscanHigherRandIndexEntry = null;
        double higherRandIndexValue = 0;
        double randIndexValue = 0;
        
        if (dbscanClustersInThisSecond != null) {
            for (CSVEntry dbscanEntry : dbscanClustersInThisSecond) {
                randIndexValue = randIndex(dg2cepEntry, dbscanEntry);
                if (higherRandIndexValue <= randIndexValue) {
                    higherRandIndexValue = randIndexValue;
                    dbscanHigherRandIndexEntry = dbscanEntry;
                }
            }
        }
        
        return dbscanHigherRandIndexEntry;
    }
    
    private double randIndex(CSVEntry dg2cepEntry, CSVEntry dbscanEntry) {
        if (dbscanEntry == null) { return 0; }
        
        Set<String> dg2cepSet = dg2cepEntry.getContent();
        Set<String> dbscanSet = dbscanEntry.getContent();
        
        Set<String> truePositive = new HashSet<String>(dg2cepSet);
        truePositive.retainAll(dbscanSet);
      
        Set<String> falsePositive = new HashSet<String>(dg2cepSet);
        falsePositive.removeAll(dbscanSet);

        Set<String> falseNegative = new HashSet<String>(dbscanSet);
        falseNegative.removeAll(dg2cepSet);
      
        double TP = truePositive.size();
        double FP = falsePositive.size();
        double FN = falseNegative.size();
          
        return TP / (TP + FP + FN);
    }

	/**
	 * Main Routine.
	 * 
	 * @param args a CSV filename.
	 * @throws IOException If log files cannot be created.
	 * @throws IllegalAccessException if args is null. 
	 */
	public static void main(String[] args) throws IOException {
	    if (args.length != 3) {
	        System.out.println("USAGE: java -jar CSVRandIndex dg2cepFile.csv dbscanFile.csv outputFile.csv");
	    }
	    
	    CSVRandIndex csvtool = new CSVRandIndex(args[0], args[1], args[2]);
	    csvtool.calculateAllRandIndex();
	}
}
